﻿#include "pinger.hpp"
#include "wlog.hpp"

using namespace std;

pinger::pinger(asio::io_context& io_service, const char* destination)
	:resolver_(io_service),
	socket_(io_service, icmp::v4()),
	timer_(io_service), 
	sequence_number_(0),
	num_replies_(0),
	state_(0),
	io_context_(io_service)
{
	icmp::resolver::query query(icmp::v4(), destination, "");
	destination_ = *resolver_.resolve(query);//目标地址

	dest_ip_ = destination;
	start_send();
	start_receive();
}

pinger::~pinger() {
	stop_steady_timer();
}



void pinger::start_send() {
	std::string body("\"Hello!\" from Asio ping.");

	// Create an ICMP header for an echo request.
	icmp_header echo_request;
	echo_request.type(icmp_header::echo_request);
	echo_request.code(0);
	echo_request.identifier(get_identifier());
	echo_request.sequence_number(++sequence_number_);
	compute_checksum(echo_request, body.begin(), body.end());

	// Encode the request packet.
	asio::streambuf request_buffer;
	std::ostream os(&request_buffer);
	os << echo_request << body;

	// Send the request.
	time_sent_ = asio::steady_timer::clock_type::now();
	try
	{
		socket_.send_to(request_buffer.data(), destination_);
	}
	catch (const std::exception&)
	{

	}
	

	timer_.expires_at(time_sent_ + chrono::seconds(5));
	timer_.async_wait(std::bind(&pinger::handle_timeout, this, std::placeholders::_1));
}


void pinger::handle_timeout(boost::system::error_code const& ec)
{
	if (ec) {
		return;
	}
	
	socket_.cancel();
	socket_ = icmp::socket(io_context_, icmp::v4());
	start_send();
	start_receive();
	
}
void pinger::start_receive() {
	
	reply_buffer_.consume(reply_buffer_.size());

	

	// Wait for a reply. We prepare the buffer to receive up to 64KB.
	socket_.async_receive(reply_buffer_.prepare(65536),//准备缓冲区
		std::bind(&pinger::handle_receive, this, std::placeholders::_1, std::placeholders::_2));
}

void pinger::handle_receive(asio::error_code ec, std::size_t length) {

	if (ec) {
		
		/*if (state_ == 1) {
			ELOG << "destip = " << dest_ip_ << "ping failed";
		}*/
		state_ = 0;
		
		return;
	}

	// The actual number of bytes received is committed to the buffer so that we
		// can extract it using a std::istream object.
	reply_buffer_.commit(length);//转移到streambuf中，


	// Decode the reply packet.
	std::istream is(&reply_buffer_);
	ipv4_header ipv4_hdr;
	icmp_header icmp_hdr;
	is >> ipv4_hdr >> icmp_hdr;

	// We can receive all ICMP packets received by the host, so we need to
	// filter out only the echo replies that match the our identifier and
	// expected sequence number.

	

	if (is && icmp_hdr.type() == icmp_header::echo_reply
		&& icmp_hdr.identifier() == get_identifier()
		 && dest_ip_ == ipv4_hdr.source_address().to_string()
		&& icmp_hdr.sequence_number() == sequence_number_  )
	{
		//num_replies_++;
		// If this is the first reply, interrupt the five second timeout.
		//if (num_replies_++ == 0)
		//auto now = chrono::high_resolution_clock::now();
		// Print out some information about the reply packet.
		//posix_time::ptime now = posix_time::microsec_clock::universal_time();
		/*ELOG << length - ipv4_hdr.header_length()
			<<" dest_ip "<< dest_ip_
			<< ", bytes from " << ipv4_hdr.source_address()
			<<" send_seq "<< sequence_number_
			<< ": icmp_seq=" << icmp_hdr.sequence_number()
			<< ", ttl=" << ipv4_hdr.time_to_live()
			<< ", time=" << (now - time_sent_).count() << " ms";*/
		/*if (state_ == 0) {
			ELOG << "destip = " << dest_ip_ << "ping success";
		}*/
		
		state_ = 1;
			
	}
	else {
		start_receive();
	}
	
}



void pinger::stop_steady_timer() {
	timer_.cancel();
}

uint16_t pinger::get_identifier()
{
	return static_cast<uint16_t>(reinterpret_cast<uintptr_t>(this));

}